package ru.reaperoq.taskmanager.controllers

import jakarta.servlet.http.HttpServletRequest
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import ru.reaperoq.taskmanager.dto.AddTaskRequest
import ru.reaperoq.taskmanager.dto.Response
import ru.reaperoq.taskmanager.dto.TaskResponse
import ru.reaperoq.taskmanager.exceptions.TaskManagerException
import ru.reaperoq.taskmanager.getUser
import ru.reaperoq.taskmanager.getUserId
import ru.reaperoq.taskmanager.models.Task
import ru.reaperoq.taskmanager.models.User
import ru.reaperoq.taskmanager.repositories.FileRepository
import ru.reaperoq.taskmanager.repositories.TaskRepository
import ru.reaperoq.taskmanager.repositories.UserRepository
import ru.reaperoq.taskmanager.services.JwtService
import java.time.Instant
import java.time.ZoneId
import java.util.*

@RestController
@RequestMapping("/api/tasks")
class TaskController(
    private val repo: TaskRepository,
    private val userRepo: UserRepository,
    private val fileRepo: FileRepository,
    private val jwtService: JwtService,
) {

    @GetMapping("/my")
    fun getTasks(req: HttpServletRequest): Response<*> {
        return Response.success(
            repo.getTasksByAuthorIdAndParentIsNull(req.getUserId(userRepo, jwtService)).map {
                it.toResponse()
            }
        )
    }

    @GetMapping("/{uuidString}")
    fun getSharedTask(@PathVariable uuidString: String): Response<TaskResponse> {
        val uuid = UUID.fromString(uuidString)
        val task = repo.findTaskBySharedId(uuid) ?: throw TaskManagerException.NotFound("Задача не найдена")
        return Response.success(task.toResponse())
    }

    @GetMapping("/{uuidString}/files/{fileId}")
    fun getSharedTaskFile(
        @PathVariable uuidString: String,
        @PathVariable fileId: Long,
        req: HttpServletRequest
    ): ResponseEntity<*> {
        val uuid = UUID.fromString(uuidString)
        val task = repo.findTaskBySharedId(uuid) ?: throw TaskManagerException.NotFound("Задача не найдена")
        val file = task.files.toList().find { it.id == fileId } ?: throw TaskManagerException.NotFound("Файл не найден")
        return ResponseEntity
            .status(HttpStatus.OK)
            .headers(HttpHeaders()
                .also {
                    it.contentType = MediaType.parseMediaType(file.mimeType)
                })
            .body(file.data)

    }

    @PutMapping("/{id}/share")
    fun shareTask(req: HttpServletRequest, @PathVariable id: Long): Response<String> {
        val task = checkTaskBelongsToUserAngGet(req, id)
        val uuid = UUID.randomUUID()
        task.sharedId = uuid
        repo.save(task)
        return Response.success(
            uuid.toString()
        )
    }

    @PutMapping("/{id}/addFile")
    fun addFile(
        req: HttpServletRequest,
        @PathVariable id: Long,
        @RequestParam("file_id") fileId: Long
    ): Response<TaskResponse> {
        val userId = req.getUserId(userRepo, jwtService)
        val task = repo.findTaskById(id) ?: throw TaskManagerException.NotFound("Задача не найдена")
        val file = fileRepo.findFileById(fileId) ?: throw TaskManagerException.NotFound("Файл не найден")
        if (task.author.id != userId)
            throw TaskManagerException.NotFound("Задача не найдена")
        if (!file.users.map { it.id }.contains(userId))
            throw TaskManagerException.NotFound("Файл не найден")
        task.files.add(file)
        return Response.success(
            repo.save(task).toResponse()
        )
    }

    @PutMapping("{id}/removeFile")
    fun removeFile(
        req: HttpServletRequest,
        @PathVariable id: Long,
        @RequestParam("file_id") fileId: Long
    ): Response<TaskResponse> {
        val userId = req.getUserId(userRepo, jwtService)
        val task = repo.findTaskById(id) ?: throw TaskManagerException.NotFound("Задача не найдена")
        if (task.author.id != userId)
            throw TaskManagerException.NotFound("Задача не найдена")
        if (!task.files.map { it.id }.contains(fileId))
            throw TaskManagerException.NotFound("Файл не найден")
        task.files.removeIf { it.id == fileId }
        return Response.success(
            repo.save(task).toResponse()
        )
    }

    @PutMapping("/{id}/updateStatus")
    fun updateStatus(
        req: HttpServletRequest,
        @PathVariable id: Long,
        @RequestParam("status") statusReq: String
    ): Response<TaskResponse> {
        val status = try {
            Task.Status.valueOf(statusReq.uppercase())
        } catch (_: Exception) {
            throw TaskManagerException.NotFound("Статус не найден")
        }
        val task = checkTaskBelongsToUserAngGet(req, id)
        task.status = status
        return Response.success(
            repo.save(task).toResponse()
        )
    }


    @PostMapping("/create")
    @ResponseStatus(HttpStatus.CREATED)
    fun addTask(@RequestBody task: AddTaskRequest, req: HttpServletRequest): Response<TaskResponse> {
        val user = req.getUser(userRepo, jwtService)
        if (task.parentId?.let { repo.findTaskById(it)?.parent } != null) {
            throw TaskManagerException.BadRequest(
                message = "Подзадача не может иметь подзадачу"
            )
        }
        return Response.success(
            repo.save(
                task.toModel(user)
            ).toResponse()
        )
    }

    @DeleteMapping("/{id}/delete")
    fun deleteTask(req: HttpServletRequest, @PathVariable id: Long): Response<List<Long>> {
        val task = checkTaskBelongsToUserAngGet(req, id)
        val subtasks = task.subtasks
        subtasks.forEach { subtask ->
            repo.delete(subtask)
        }
        repo.delete(task)
        return Response.success(subtasks.map { it.id!! } + listOf(task.id!!))
    }

    private fun AddTaskRequest.toModel(user: User): Task {
        return Task(
            title = this.title,
            author = user,
            timeStart = Instant.ofEpochSecond(this.timeStart).atZone(ZoneId.systemDefault()).toLocalDateTime(),
            timeEnd = this.timeEnd?.let { Instant.ofEpochSecond(it).atZone(ZoneId.systemDefault()).toLocalDateTime() },
            parent = parentId?.let { repo.findTaskById(it) }
        )
    }

    private fun checkTaskBelongsToUserAngGet(req: HttpServletRequest, taskId: Long): Task {
        val userId = req.getUserId(userRepo, jwtService)
        val task = repo.findTaskById(taskId) ?: throw TaskManagerException.NotFound("Задача не найдена")
        if (task.author.id != userId) throw TaskManagerException.NotFound("Задача не найдена")
        return task
    }
}